﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;

namespace VJoinFile
{
    public partial class frmVJoinFile : Form
    {
        #region Fields
        private bool _isCancel = false;
        private int _currentPercent = 0;
        private string _currentFile = null;
        private int _currentFilePercent = 0;
        private long _currentTotalFileWrited = 0;
        private long _currentTotalFileWritedTemp = 0;
        
        #endregion

        #region Constructor
        public frmVJoinFile()
        {
            InitializeComponent();
            
        }
        #endregion

        #region Component Events
        private void btnBrowseFileJoin_Click(object sender, EventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            if (txtFileJoin.Text != string.Empty)
            {
                string dir = Path.GetDirectoryName(txtFileJoin.Text);
                dlg.InitialDirectory = dir;
            }

            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtFileJoin.Text = dlg.FileName;
                List<FileInfo> filesJoin = GetFilesJoin();
                ShowSizeFilesJoin(filesJoin.Sum(c => c.Length));
            }
        }

        private void btnBrowseOutputDirectory_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            if (txtOutputDirectory.Text != string.Empty)
            {
                dlg.SelectedPath = txtOutputDirectory.Text;
            }

            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtOutputDirectory.Text = dlg.SelectedPath;
            }
        }

        private void btnStart_Click(object sender, EventArgs e)
        {   
            if (btnStart.Tag == null || (int)btnStart.Tag == 0)
            {   
                _isCancel = false;
                lblStatus.Text = "Status: Working...";
                prgPercent.Value = 0;
                _currentFilePercent = 0;
                _currentFile = null;
                _currentTotalFileWrited = 0;
                _currentTotalFileWritedTemp = 0;

                List<FileInfo> filesJoin = GetFilesJoin();
                long totalSize = filesJoin.Sum(x => x.Length);
                //show file size
                ShowSizeFilesJoin(totalSize);
                Thread thread = new Thread(delegate()
                {
                    string fullPath = txtOutputDirectory.Text + Path.GetFileNameWithoutExtension(txtFileJoin.Text);
                    UpdateUIDelegate updatePercent = new UpdateUIDelegate(UpdateProgressBar);
                    UpdateUIDelegate updateFilePercent = new UpdateUIDelegate(UpdateProgressBarCurrentFile);
                    int tempPercent = 0;
                    CalculatingSpeed();
                    using (FileStream fs = new FileStream(fullPath, FileMode.Create, FileAccess.Write))
                    {
                        int sizeBuffer = 8192;
                        byte[] buffer = new byte[sizeBuffer];
                        int n = 0;
                        foreach (FileInfo f in filesJoin)
                        {
                            if (_isCancel)
                            {
                                break;
                            }
                            //open file to read
                            using (FileStream fsPart = new FileStream(f.FullName, FileMode.Open, FileAccess.Read))
                            {
                                long totalFileSize = f.Length;
                                int tempPercentFile = 0;
                                _currentFilePercent = 0;
                                long currentFileReaded = 0;
                                _currentFile = f.Name;
                                this.Invoke(new UpdateUIDelegate(UpdateCurrentFile));
                                while ((n = fsPart.Read(buffer, 0, sizeBuffer)) > 0)
                                {
                                    if (_isCancel)
                                    {
                                        break;
                                    }
                                    //add more readed data
                                    currentFileReaded += n;
                                    _currentTotalFileWrited += n;

                                    //update ui - read file
                                    tempPercentFile = (int)(currentFileReaded * 100 / totalFileSize);
                                    if (tempPercentFile != _currentFilePercent)
                                    {
                                        _currentFilePercent = tempPercentFile;
                                        this.Invoke(updateFilePercent);
                                    }

                                    //update ui - total file destination
                                    tempPercent = (int)(_currentTotalFileWrited * 100 / totalSize);
                                    if (tempPercent != _currentPercent)
                                    {
                                        _currentPercent = tempPercent;
                                        this.Invoke(updatePercent);
                                    }
                                    fs.Write(buffer, 0, n);
                                }
                            }
                        }
                    }
                    //check cancel - delete file immediately
                    if (_isCancel)
                    {
                        this.Invoke(new UpdateUIDelegate(UpdateStatusCanceled));
                        File.Delete(fullPath);
                    }
                    else
                    {
                        this.Invoke(new UpdateUIDelegate(UpdateStatusDone));
                    }
                    _isCancel = false;
                });
                thread.Start();
                btnStart.Tag = 1;
                btnStart.Text = "&Stop";                
            }
            else
            {
                _isCancel = true;
                btnStart.Tag = 0;
                btnStart.Text = "&Start";
            }
        }
        #endregion

        #region Helpers
        /// <summary>
        /// update progress bar delegate
        /// </summary>
        private delegate void UpdateUIDelegate();
        /// <summary>
        /// update progress bar
        /// </summary>
        private void UpdateProgressBar()
        {
            prgPercent.Value = _currentPercent;
        }

        private void UpdateStatusCanceled()
        {
            lblStatus.Text = "Status: Canceled";
        }

        private void UpdateStatusDone()
        {
            lblStatus.Text = "Status: Done";
        }

        private void UpdateCurrentFile()
        {
            lblCurrentFile.Text = _currentFile;
        }

        private void UpdateProgressBarCurrentFile()
        {
            prgCurrentFile.Value = _currentFilePercent;
        }

        /// <summary>
        /// Get files join
        /// </summary>
        /// <returns></returns>
        private List<FileInfo> GetFilesJoin()
        {
            FileInfo finfo = new FileInfo(txtFileJoin.Text);
            string fileName = Path.GetFileNameWithoutExtension(finfo.Name);
            string[] files = Directory.GetFiles(finfo.DirectoryName, Path.GetFileNameWithoutExtension(fileName) + "*");
            List<FileInfo> filesJoin = (from file in files
                                        orderby int.Parse(Path.GetExtension(file).Replace(".", ""))
                                        select new FileInfo(file)).ToList();
            return filesJoin;
        }

        /// <summary>
        /// Show size files join
        /// </summary>
        ///<param name="size">File size</param>
        private void ShowSizeFilesJoin(long size)
        {
            string fileSize = GetFormatFileSize(size);
            lblSize.Text = "Size: " + fileSize;
        }

        /// <summary>
        /// Get format file size
        /// </summary>
        /// <param name="files"></param>
        /// <returns></returns>
        private string GetFormatFileSize(long size)
        {
            string result = "";
            if (size < 1024)
            {
                result = size + " bytes";
            }
            else if (size < 1024000)
            {
                result = Math.Round(size / 1024.0, 2) + " KB";
            }
            else if (size < 1024000000)
            {
                result = Math.Round(size / 1024000.0, 2) + " MB";
            }
            else if (size < 1024000000000)
            {
                result = Math.Round(size / 1024000000.0, 2) + " GB";
            }
            else if (size < 1024000000000)
            {
                result = Math.Round(size / 1024000000.0, 2) + " TB";
            }

            return result;
        }

        private void CalculatingSpeed()
        {
            Thread thread = new Thread(delegate()
            {
                int timeSleep = 500;
                int timeMultiple = 1000 / timeSleep;
                while (!_isCancel)
                {
                    long total = _currentTotalFileWrited - _currentTotalFileWritedTemp;
                    string text = GetFormatFileSize(total * timeMultiple);
                    _currentTotalFileWritedTemp = _currentTotalFileWrited;
                    this.Invoke(new UpdateUIDelegate(delegate()
                    {
                        lblSpeed.Text = "Speed: " + GetFormatFileSize(total * timeMultiple) + "/sec";
                    }));                    
                    Thread.Sleep(timeSleep);
                }
            });
            thread.Start();
        }
        #endregion

        private void frmVJoinFile_FormClosing(object sender, FormClosingEventArgs e)
        {
            _isCancel = true;
            Thread.Sleep(200);
            Environment.Exit(Environment.ExitCode);
        }

        private void frmVJoinFile_Load(object sender, EventArgs e)
        {
            //_timerSpeed.Start();
        }
    }
}
